package com.cyx.friend.service.user.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.cyx.common.core.constants.CacheConstants;
import com.cyx.common.core.constants.Constants;
import com.cyx.common.core.constants.HttpConstants;
import com.cyx.common.core.domain.LoginUser;
import com.cyx.common.core.domain.LoginUserVO;
import com.cyx.common.core.domain.R;
import com.cyx.common.core.enums.ResultCode;
import com.cyx.common.core.enums.UserIdentity;
import com.cyx.common.core.enums.UserStatus;
import com.cyx.common.core.utils.ThreadLocalUtil;
import com.cyx.common.message.service.NetEaseSmsService;
import com.cyx.common.redis.service.RedisService;
import com.cyx.common.security.exception.ServiceException;
import com.cyx.common.security.service.TokenService;
import com.cyx.friend.domain.user.User;
import com.cyx.friend.domain.user.dto.UserDTO;
import com.cyx.friend.domain.user.dto.UserEditDTO;
import com.cyx.friend.domain.user.vo.UserVO;
import com.cyx.friend.manager.UserCacheManager;
import com.cyx.friend.mapper.user.UserMapper;
import com.cyx.friend.service.user.IUserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
@RefreshScope
public class UserServiceImpl implements IUserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private NetEaseSmsService tencentSmsService;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private RedisService redisService;

    @Autowired
    private UserCacheManager userCacheManager;

    @Value("${sms.code-expiration:5}")
    private Long phoneCodeExpiration;

    @Value("${sms.send-limit:3}")
    private Integer sendLimit;

    @Value("${sms.is-send:false}")
    private boolean isSend; // 开关打开true  开关关闭false

    @Value("${jwt.secret}")
    private String secret;

    @Value("${file.oss.downloadUrl}")
    private String downloadUrl;

    @Override
    public boolean sendCode(UserDTO userDTO) {
        if(StringUtils.isEmpty(userDTO.getPhone())) {
            throw new ServiceException(ResultCode.USER_PHONE_NULL);
        }
        // 检查手机号是否合法
        if(!checkPhone(userDTO.getPhone())) {
            throw new ServiceException(ResultCode.FAILED_USER_PHONE);
        }
        String phoneCodeKey = getPhoneCodeKey(userDTO.getPhone());
        Long expire = redisService.getExpire(phoneCodeKey, TimeUnit.MINUTES);
        // 限制发送验证码的频率，如果一分钟之内多次获取验证码就抛异常
        if(expire != null && phoneCodeExpiration - expire < 1) {
            throw new ServiceException(ResultCode.FAILED_FREQUENT);
        }
        // 每天的验证码获取次数有一个限制 50次 第二天清零，重新计数
        String codeTimeKey = getCodeTimeKey(userDTO.getPhone());
        // 获取当日获取验证码的次数
        Long sendTimes = redisService.getCacheObject(codeTimeKey, Long.class);
        // 超过限制
        if(sendTimes != null && sendTimes > sendLimit) {
            throw new ServiceException(ResultCode.FAILED_TIME_LIMIT);
        }
        // 生成6位随机数作为验证码
        String code = isSend ? RandomUtil.randomNumbers(6) : Constants.DEFAULT_CODE;
        // 将验证码和手机号对应起来存储到redis中
        redisService.setCacheObject(phoneCodeKey, code, phoneCodeExpiration, TimeUnit.MINUTES);
        if(isSend) {
            // 发送信息
            boolean sendMobileCode = tencentSmsService.sendMobileCode(userDTO.getPhone(), code);
            if (!sendMobileCode) {
                throw new ServiceException(ResultCode.FAILED_SEND_CODE);
            }
        }
        // 获取一次验证码 次数就加一
        redisService.increment(codeTimeKey);
        // 第一次发送时，设置过期时间
        if(sendTimes == null) {
            long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),
                    LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));
            redisService.expire(codeTimeKey, seconds);
        }
        return true;
    }

    @Override
    public String codeLogin(String phone, String code) {

        // 验证码比对
        checkCode(phone, code);
        // TODO 有脏数据该怎么办？
//        List<User> userList = userMapper.selectList(new LambdaQueryWrapper<User>()
//                .eq(User::getPhone, phone));
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getPhone, phone));
        // 新用户    注册
        if(user == null) {
            user = new User();
            // 用户注册
            user.setPhone(phone);
            user.setStatus(UserStatus.Normal.getValue());
            userMapper.insert(user);
        }
        return tokenService.createToken(user.getUserId(), secret, UserIdentity.USER.getValue(), user.getNickName(), user.getHeadImage());


//        if(CollUtil.isNotEmpty(userList)) {
//            User user = userList.get(0);
//            String phoneCodeKey = getPhoneCodeKey(phone);
//            String cacheCode = redisService.getCacheObject(phoneCodeKey, String.class);
//            if(StrUtil.isEmpty(cacheCode)) {
//                throw new ServiceException(ResultCode.FAILED_INVALID_CODE);
//            }
//            if(cacheCode.equals(code)) {
//                throw new ServiceException(ResultCode.FAILED_ERROR_CODE);
//            }
//            // 验证码比对成功
//            redisService.deleteObject(phoneCodeKey);
//            return tokenService.createToken(user.getUserId(), secret, UserIdentity.ADMIN.getValue(), user.getNickName());
//        }
    }

    @Override
    public boolean logout(String token) {
        //如果前端设置了令牌前缀，则裁剪掉前缀
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        // 根据token获取登录用户的信息
        return tokenService.deleteLoginUser(token, secret);
    }

    @Override
    public R<LoginUserVO> info(String token) {
        //如果前端设置了令牌前缀，则裁剪掉前缀
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        // 根据token获取登录用户的信息
        LoginUser loginUser = tokenService.getLoginUser(token, secret);
        if(loginUser == null) {
            return R.fail();
        }
        LoginUserVO loginUserVO = new LoginUserVO();
        // 将用户昵称返回
        loginUserVO.setNickName(loginUser.getNickName());
        loginUserVO.setHeadImage(loginUser.getHeadImage());
        if(StrUtil.isNotEmpty(loginUser.getHeadImage())) {
            loginUserVO.setHeadImage(downloadUrl + loginUser.getHeadImage());;
        }
        return R.ok(loginUserVO);
    }

    @Override
    public UserVO detail() {
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        UserVO userVO = userCacheManager.getUserById(userId);
        if (userVO == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        if (StrUtil.isNotEmpty(userVO.getHeadImage())) {
            userVO.setHeadImage(downloadUrl + userVO.getHeadImage());
        }
        return userVO;
    }

    @Override
    public int edit(UserEditDTO userEditDTO) {
        // 先从ThreadLocal中获取当前用户id
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        // 再从数据库中查找对应的user
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        // 修改用户信息
        user.setNickName(userEditDTO.getNickName());
        user.setSex(userEditDTO.getSex());
        user.setSchool(userEditDTO.getSchool());
        user.setMajor(userEditDTO.getMajor());
        user.setPhone(userEditDTO.getPhone());
        user.setEmail(userEditDTO.getEmail());
        user.setWechat(userEditDTO.getWechat());
        user.setIntroduce(userEditDTO.getIntroduce());
        //更新用户缓存
        userCacheManager.refreshUser(user);
        // 更新登录用户缓存
        tokenService.refreshLoginUser(user.getNickName(),user.getHeadImage(),
                ThreadLocalUtil.get(Constants.USER_KEY, String.class));
        return userMapper.updateById(user);
    }

    @Override
    public int updateHeadImage(String headImage) {
        Long userId = ThreadLocalUtil.get(Constants.USER_ID, Long.class);
        if (userId == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        user.setHeadImage(headImage);
        //更新用户缓存
        userCacheManager.refreshUser(user);
        tokenService.refreshLoginUser(user.getNickName(),user.getHeadImage(),
                ThreadLocalUtil.get(Constants.USER_KEY, String.class));
        return userMapper.updateById(user);
    }

    private void checkCode(String phone, String code) {
        String phoneCodeKey = getPhoneCodeKey(phone);
        String cacheCode = redisService.getCacheObject(phoneCodeKey, String.class);
        if(StrUtil.isEmpty(cacheCode)) {
            throw new ServiceException(ResultCode.FAILED_INVALID_CODE);
        }
        if(!cacheCode.equals(code)) {
            throw new ServiceException(ResultCode.FAILED_ERROR_CODE);
        }
        // 验证码比对成功
        redisService.deleteObject(phoneCodeKey);
    }

    // 检验手机号码是否合法
    public static boolean checkPhone(String phone) {
        Pattern regex = Pattern.compile("^1[2|3|4|5|7|8|9][0-9]\\d{8}$");
        Matcher matcher = regex.matcher(phone);
        return matcher.matches();
    }

    private String getPhoneCodeKey(String phone) {
        return CacheConstants.PHONE_CODE_KEY + phone;
    }

    private String getCodeTimeKey(String phone) {
        return CacheConstants.CODE_TIME_KEY + phone;
    }
}
